/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.validator.engine;

import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import ch.trackedbean.common.*;
import ch.trackedbean.util.*;
import ch.trackedbean.validator.*;

/**
 * Validation engine.
 * 
 * @author M. Hautle
 */
public class Validator implements IValidator {
	/** Return value of {@link #getValue(String, Object)} if no getter for the specified property is registed. */
	protected static final Object NO_GETTER = new Object();

	/** The type for which gets validated by this object. */
	protected final Class<?> type;

	/** The business validators of this validator. */
	protected List<IBusinessValidator> businessValidators = new ArrayList<IBusinessValidator>(1);

	/** The validators mapped on their property names. */
	protected Map<String, List<IPropertyValidator>> validators = new HashMap<String, List<IPropertyValidator>>();

	/** The getter methods for the properties of {@link #simpleProperties}. */
	protected Map<String, Method> getters = new HashMap<String, Method>();

	/**
	 * List holding the names of the simple properties to validate.<br>
	 * This list must have the same content as the key set of {@link #validators}
	 */
	protected List<String> simpleProperties = new ArrayList<String>();

	/** List holding the {@link DepthValidator}s of this validator. */
	protected List<DepthValidator> depthValidationProperties = new ArrayList<DepthValidator>();

	/**
	 * Default constructor.
	 * 
	 * @param type The type to validate
	 * @throws ValidatorInitialisationException If something went wrong
	 */
	public Validator(Class<?> type) throws ValidatorInitialisationException {
		this.type = type;
	}

	/**
	 * Adds a business validator.<br>
	 * The passed business validator will only be executed if the property validations were error free!
	 * 
	 * @param val The validator to add
	 */
	public void addBusinessValidator(IBusinessValidator val) {
		businessValidators.add(val);
	}

	/**
	 * Adds a property for deep validation.<br>
	 * This is used for properties holding beans which have to be validated too.
	 * 
	 * @param property The property
	 * @param message The message for an invalid property
	 * @return True if the property was added for deep validation
	 */
	public boolean addDeepValidation(String property, String message) {
		final Method read = BeanUtils.getGetter(type, property);
		if (read == null)
			return false;
		depthValidationProperties.add(new DepthValidator(property, message, read));
		return true;
	}

	/**
	 * Adds the given validator for the given property.
	 * 
	 * @param property The property
	 * @param validator The validator to add
	 * @return True if the validator was added
	 */
	public boolean addPropertyValidation(String property, IPropertyValidator validator) {
		if (!registerSimpleProperty(property))
			return false;
		getValidatorList(property).add(validator);
		return true;
	}

	/**
	 * Returns the list holding the validators of the given property.<br>
	 * A new list gets created if necessary.
	 * 
	 * @param property The property
	 * @return The list holding the validators
	 */
	private List<IPropertyValidator> getValidatorList(String property) {
		List<IPropertyValidator> vals = validators.get(property);
		if (vals == null)
			validators.put(property, vals = new ArrayList<IPropertyValidator>(1));
		return vals;
	}

	/**
	 * Registers the given property as "property to validate".<br>
	 * This method adds the getter methode of the given property to {@link #getters} if necessary.
	 * 
	 * @param property The property
	 * @return True if the property can be validated
	 */
	private boolean registerSimpleProperty(String property) {
		if (getters.containsKey(property))
			return true;
		final Method read = BeanUtils.getGetter(type, property);
		if (read == null)
			return false;
		getters.put(property, read);
		simpleProperties.add(property);
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean doesSomething() {
		return simpleProperties.size() + depthValidationProperties.size() + businessValidators.size() > 0;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean doesBusinessValidation() {
		return !businessValidators.isEmpty();
	}

	/**
	 * {@inheritDoc}
	 */
	public ValidationResult validate(Object bean) throws TechnicalException {
		final ValidationContext context = new ValidationContext(bean);
		validate(bean, context);
		return context.getResult();
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public ValidationResult validateBusinessPart(Object bean) {
		final ValidationContext context = new ValidationContext(bean);
		for (IBusinessValidator v : businessValidators)
			context.addGeneralError(v.validate(bean, context));
		return context.getResult();
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public boolean validate(Object bean, IValidationContext context) throws TechnicalException {
		boolean err = false;
		// validate the simple properties
		for (String prop : simpleProperties)
			err |= context.addErrors(prop, validate(prop, bean));
		// validate the business content
		for (IBusinessValidator v : businessValidators)
			err |= context.addGeneralError(v.validate(bean, context));
		// validate the structure properties
		for (DepthValidator prop : depthValidationProperties)
			err |= prop.validate(bean, context);
		return err;
	}

	/**
	 * {@inheritDoc}
	 */
	public IErrorDescription[] validate(String property, Object bean) throws TechnicalException {
		final Object value = getValue(property, bean);
		if (value == NO_GETTER)
			return null;
		IErrorDescription[] err = null;
		for (IPropertyValidator v : validators.get(property))
			err = appendError(err, v.validate(value));
		return err;
	}

	/**
	 * Returns the value of the given property
	 * 
	 * @param property The propertyname
	 * @param bean The bean to invoke
	 * @return The value of the given property or {@link #NO_GETTER} if the property is not registred
	 * @throws TechnicalException If something went wrong
	 */
	protected Object getValue(String property, Object bean) throws TechnicalException {
		try {
			final Method get = getters.get(property);
			if (get == null)
				return NO_GETTER;
			return get.invoke(bean);
		} catch (Exception e) {
			throw new TechnicalException("Error while reading " + property + " from " + bean, e);
		}
	}

	/**
	 * Appends the error to the {@link IErrorDescription} array.
	 * 
	 * @param err A {@link IErrorDescription} array or null
	 * @param errorDescription The error to append or null
	 * @return The resulting array
	 */
	protected IErrorDescription[] appendError(IErrorDescription[] err, IErrorDescription errorDescription) {
		if (errorDescription == null)
			return err;
		if (err == null)
			return new IErrorDescription[] { errorDescription };
		return BeanUtils.arrayAppend(err, errorDescription);
	}

	/**
	 * Appends the information from the passed validator to this one.<br>
	 * This copies the property validations but not the {@link IBusinessValidator}s.
	 * 
	 * @param val The validator
	 */
	public void appendValidatorInformation(Validator val) {
		// append the validators
		for (Entry<String, List<IPropertyValidator>> e : val.validators.entrySet()) {
			final String property = e.getKey();
			if (!registerSimpleProperty(property))
				continue;
			final List<IPropertyValidator> list = getValidatorList(property);
			for (IPropertyValidator v : e.getValue())
				list.add(v);
		}
		// append the depth properties
		depthValidationProperties.addAll(val.depthValidationProperties);
	}

	/**
	 * Appends the simple validators for the given property from the passed validator to this one.
	 * 
	 * @param val The validator
	 * @param srcProperty The property from which to inherit (on the passed validator)
	 * @param dstProperty The property where to append the inherited validations (on this validator)
	 */
	public void appendSimpleValidators(Validator val, String srcProperty, String dstProperty) {
		final List<IPropertyValidator> validators = val.validators.get(srcProperty);
		if (validators == null || !registerSimpleProperty(dstProperty))
			return;
		// add the validators
		final List<IPropertyValidator> list = getValidatorList(dstProperty);
		for (IPropertyValidator v : validators)
			list.add(v);
	}
}
